import { Subject } from "rxjs";
import { AppTask } from "../../app.task.class";
import { logger } from "../../../configs/log.config";
import { SendboxKey, V3proSubjectMessage } from "../model/v3pro.model";
import { sleep, to } from "../../../utils/cmm.util";
import { diff } from "deep-diff";
import { appConfig } from "../../../configs/env.config.load";
import { v3proScreenAllStatusApi, v3proScreenApi } from "../../../api/v3pro-api/v3pro.screen.api";
import { V3proAlertMessageErrTypeEnum, V3proSubjectMessageTypeEnum } from "../model/v3pro.enum";
import { cloneDeep } from "lodash";
import { V3proScreenLayoutHelper } from "../helper/v3pro.screen.layout.helper";
import { V3proScreen, V3proScreenAllStatus } from "../../../api/v3pro-api/v3pro.api.response.model";
import { V3proAllStatusLambda } from "../helper/v3pro.all.status.lambda.helper";
import { V3proFileHelper } from "../helper/v3pro.file.helper";
import { V3proScreenImageHelper } from "../helper/v3pro.screen.image.helper";

/**
 * 健康检查任务基类
 */
export abstract class HealthCheckTask extends AppTask {
  protected __observable?: Subject<V3proSubjectMessage>;

  /**
   * 获取间隔时间
   * @protected
   */
  getInterval(): number {
    return appConfig.v3pro.collectTimer.interval * 1000;
  }

  /**
   * 添加观察者
   * @param observable
   */
  setObservable(observable: Subject<V3proSubjectMessage>): HealthCheckTask {
    this.__observable = observable;
    return this;
  }

  /**
   * 暂停任务
   */
  __pause(): Promise<boolean> {
    return Promise.resolve(false);
  }

  /**
   * 恢复任务
   */
  __restore(): Promise<boolean> {
    return Promise.resolve(false);
  }

  /**
   * 运行任务
   */
  async __run(): Promise<void> {
    if (!this.__observable) {
      logger.error("[HealthCheckTask] no observable");
      return;
    }
    await this.__tryCheck.call(this, this.__observable).then();
    await this.__awaitChildTask();
  }

  /**
   * 尝试检查
   * @param observable
   * @private
   */
  private async __tryCheck(observable: Subject<V3proSubjectMessage>) {
    try {
      if (!this.__runnable()) return;
      await this.__check(observable);
    } catch (e) {
      logger.error("check fail: ", e);
    }
  }

  /**
   * 是否允许本次执行
   */
  __runnable(): boolean {
    return true;
  }

  /**
   * 健康检查函数
   * @param observable 观察者
   */
  abstract __check(observable: Subject<V3proSubjectMessage>): Promise<void>;

  /**
   * 停止任务
   * 1. 将所有请求取消
   */
  async __stop(): Promise<boolean> {
    this.__childTasks.forEach((task) => task.__stop().then());
    return Promise.resolve(true);
  }

  /**
   * 子任务
   * @private
   */
  private __childTasks: AppTask[] = [];

  /**
   * 添加子任务
   * @param task
   * @protected
   */
  protected __startChildTask(task: HealthCheckTask) {
    this.__childTasks.push(task);
    task.setObservable(this.__observable!);
    task.start().then();
  }

  protected __startChildTasks(...tasks: HealthCheckTask[]) {
    tasks.forEach((task) => this.__startChildTask(task));
  }

  /**
   * 等待子任务结束
   * @private
   */
  private async __awaitChildTask() {
    while (true) {
      if (!this.__childTasks.some((task) => task.isRunning())) break;
      await sleep(100);
    }
  }
}

/**
 * 自动提取屏幕信息，并分别传入 __checkScreen
 */
export abstract class HealthCheckWithScreenTask<D> extends HealthCheckTask {
  // 所有屏幕状态，一般作为子任务的时候会有该值
  private readonly __allStatus?: V3proScreenAllStatus;

  /**
   * 构造函数，只有在子类的情况下才需要传入 allStatus
   * @param allStatus
   * @param __screenLayoutHelper 屏幕布局辅助类
   */
  constructor(
    allStatus?: V3proScreenAllStatus,
    private __screenLayoutHelper?: V3proScreenLayoutHelper,
  ) {
    super();
    if (allStatus) this.__allStatus = allStatus;
  }
  /**
   * 最后一次请求的关键参数
   * @private
   */
  private __lastKeyParams: D | undefined;

  /**
   * 生成关键参数（用于diff计算）
   * @param allStatus 该 allStatus 为深度拷贝后的 allStatus
   * @param screen
   * @param screenLayoutHelper
   */
  abstract genKeyParams(
    allStatus: V3proScreenAllStatus,
    screen: V3proScreen,
    screenLayoutHelper: V3proScreenLayoutHelper,
  ): Promise<D>;

  /**
   * 检查最终结果
   * @param keyParams
   * @param screenId
   * @param allStatus
   * @param screenLayoutHelper
   */
  abstract checkResult(
    keyParams: D,
    screenId: string,
    allStatus: V3proScreenAllStatus,
    screenLayoutHelper: V3proScreenLayoutHelper,
  ): Promise<V3proSubjectMessage>;

  /**
   * 发送盒检查结果通用函数
   * @param keyParams
   * @param screenId
   * @param allStatus
   * @param screenLayoutHelper
   * @private
   */
  protected async __checkSendBoxResult(
    keyParams: SendboxKey[],
    screenId: string,
    allStatus: V3proScreenAllStatus,
    screenLayoutHelper: V3proScreenLayoutHelper,
  ) {
    const message = new V3proSubjectMessage();
    if (keyParams.length === 0) return message;
    message.setErrStatus();
    keyParams.forEach((box) => screenLayoutHelper.setSendBoxErrById(box.id), 2);
    message.files = [
      await V3proFileHelper.sendBoxKey2File(keyParams),
      await new V3proScreenImageHelper(screenLayoutHelper, ["scanBoard"])
        .draw()
        .export2AlertMessageFile(),
    ];
    return message;
  }

  /**
   * 运行检查任务
   * 1. 查找屏幕数量
   * 2. 遍历屏幕，依次检查
   * @param observable
   */
  async __check(observable: Subject<V3proSubjectMessage>): Promise<void> {
    try {
      // 如果存在 allStatus, 说明是子类，无需遍历所有屏幕
      if (this.__allStatus) {
        await this.__genChecks(this.__allStatus.deviceInfo);
        return;
      }
      // 获取所有屏幕
      const { requestDatas } = await v3proScreenApi(this.__addCancelToken());
      if (requestDatas && requestDatas.length > 0) {
        const checks = requestDatas.map(this.__genChecks.bind(this));
        await Promise.all(checks);
      }
    } catch (e) {
      logger.error("[HealthCheckWithScreenTask::__check] ", e);
    }
  }

  /**
   * 需要文件上传
   */
  abstract isNeedUploadFile(
    message: V3proSubjectMessage,
    screenId: string,
    allStatus: V3proScreenAllStatus,
  ): boolean;

  /**
   * 生成 mqtt topic
   * @param message
   * @param screenId
   * @param allStatus
   */
  abstract genSRMqttTopic(
    message: V3proSubjectMessage,
    screenId: string,
    allStatus: V3proScreenAllStatus,
  ): string;

  /**
   * 获取 v3pro 消息类型
   *
   */
  abstract getV3proAlertMessage(): V3proAlertMessageErrTypeEnum | undefined;

  /**
   * 获取屏幕所有状态
   * @param screenId
   * @protected
   */
  protected async getV3proScreenAllStatus(screenId: string): Promise<V3proScreenAllStatus> {
    if (this.__allStatus) return this.__allStatus!;
    const [err, res] = await to(v3proScreenAllStatusApi(screenId, this.__addCancelToken()));
    if (err) return Promise.reject(err);
    return res!;
  }

  /**
   * 错误是否启用
   * 是否上报
   */
  abstract isReportEnabled(keyParams: D, screenId: string): boolean;

  /**
   * 生成异步检查函数
   * @param screenId 屏幕id
   * @param observable 观察者
   * @private
   */
  private __genChecks({ id: screenId }: V3proScreen) {
    return new Promise(async (resolve) => {
      try {
        let screenAllStatus = await this.getV3proScreenAllStatus(screenId);
        if (this.isStopped()) return;
        screenAllStatus = V3proAllStatusLambda.accAllStatusXY(screenAllStatus);
        if (screenAllStatus) {
          const scHelper =
            this.__screenLayoutHelper ?? new V3proScreenLayoutHelper(screenAllStatus);
          const cloneStatus = cloneDeep(screenAllStatus);
          let keyParams = await this.genKeyParams(cloneStatus, cloneStatus.deviceInfo, scHelper);
          if (appConfig.starriver.enableDiffCheck) {
            const diffs = diff(keyParams, this.__lastKeyParams);
            this.__lastKeyParams = keyParams;
            if (!diffs || !diffs.length) return;
          }
          if (!this.isReportEnabled(keyParams, screenId)) return;
          const [err, res] = await to(
            this.checkResult(keyParams, screenId, screenAllStatus, scHelper),
          );
          if (err) return logger.error(err);
          if (!res) return;
          res.keyParams = keyParams;
          this.__fillCallbackInfo(res, screenId, screenAllStatus);
          this.__observable?.next(res);
        }
      } catch (e) {
        logger.error("[HealthCheckWithScreenTask::__genChecks] ", e);
      } finally {
        resolve(true);
      }
    });
  }

  /**
   * 填充固定回调信息
   * 1. 是否需要上传
   * 2. mqtt topic
   * 3. v3pro 消息类型
   * @param message
   * @param screenId
   * @param allStatus
   * @private
   */
  private __fillCallbackInfo(
    message: V3proSubjectMessage,
    screenId: string,
    allStatus: V3proScreenAllStatus,
  ) {
    message.needUploadFile = this.isNeedUploadFile(message, screenId, allStatus);
    message.srMqttTopic = this.genSRMqttTopic(message, screenId, allStatus);
    message.alertType = this.getV3proAlertMessage();
    message.screenId = screenId;
    message.msgType = this.getMessageType();
  }

  /**
   * 获取消息类型
   */
  getMessageType() {
    return V3proSubjectMessageTypeEnum.ALERT;
  }
}

/**
 * 最后一次错误缓存
 * 用于将上级通讯故障的信息合并到本次错误中
 */
export class V3proLastErrCache<T> {
  private __cache = new Map<string, T[]>();

  /**
   * 合并缓存
   * @param keyParams
   * @param normals
   * @param screenId
   * @param genKey
   * @private
   */
  mergeCache(keyParams: T[], normals: T[], screenId: string, genKey: (obj: T) => any) {
    const cache = this.__cache.get(screenId);
    if (!cache) {
      this.__cache.set(screenId, keyParams);
      return keyParams;
    }
    const existKeys = [...keyParams.map(genKey), ...normals.map(genKey)];
    const cacheErr = cache.filter((obj) => !existKeys.includes(genKey(obj)));
    const all = [...keyParams, ...cacheErr];
    this.__cache.set(screenId, all);
    return all;
  }
}
